package com.jlg.util.dom;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.jlg.beans.page.Outlink;

/**
 * 
 * @author huazhang
 * @since 2011-03-26
 */
public class DOMContentUtils
{

    private HashMap linkParams;

    public static class LinkParams
    {

        public String toString()
        {
            return (new StringBuilder()).append("LP[el=").append(elName)
                    .append(",attr=").append(attrName).append(",len=")
                    .append(childLen).append("]").toString();
        }

        public String elName;

        public String attrName;

        public int childLen;

        public LinkParams(String elName, String attrName, int childLen)
        {
            this.elName = elName;
            this.attrName = attrName;
            this.childLen = childLen;
        }
    }

    public DOMContentUtils()
    {
        linkParams = new HashMap();
    }

    public void setConf()
    {
        linkParams.clear();
        linkParams.put("a", new LinkParams("a", "href", 1));
        linkParams.put("area", new LinkParams("area", "href", 0));
        linkParams.put("form", new LinkParams("form", "action", 1));
        linkParams.put("frame", new LinkParams("frame", "src", 0));
        linkParams.put("iframe", new LinkParams("iframe", "src", 0));
        linkParams.put("script", new LinkParams("script", "src", 0));
        linkParams.put("link", new LinkParams("link", "href", 0));
        linkParams.put("img", new LinkParams("img", "src", 0));
    }

    public boolean getText(StringBuffer sb, Node node,
            boolean abortOnNestedAnchors)
    {
        return getTextHelper(sb, node, abortOnNestedAnchors, 0);
    }

    public void getText(StringBuffer sb, Node node)
    {
        getText(sb, node, false);
    }

    private boolean getTextHelper(StringBuffer sb, Node node,
            boolean abortOnNestedAnchors, int anchorDepth)
    {
        if ("script".equalsIgnoreCase(node.getNodeName()))
            return false;
        if ("style".equalsIgnoreCase(node.getNodeName()))
            return false;
        if (abortOnNestedAnchors && "a".equalsIgnoreCase(node.getNodeName())
                && ++anchorDepth > 1)
            return true;
        if (node.getNodeType() == Node.COMMENT_NODE)
            return false;
        if (node.getNodeType() == Node.TEXT_NODE)
        {
            String text = node.getNodeValue();
            text = text.replaceAll("\\s+", " ");
            text = text.trim();
            if (text.length() > 0)
            {
                if (sb.length() > 0)
                    sb.append(' ');
                sb.append(text);
            }
        }
        boolean abort = false;
        NodeList children = node.getChildNodes();
        if (children != null)
        {
            int len = children.getLength();
            int i = 0;
            do
            {
                if (i >= len)
                    break;
                if (getTextHelper(sb, children.item(i), abortOnNestedAnchors,
                        anchorDepth))
                {
                    abort = true;
                    break;
                }
                i++;
            } while (true);
        }
        return abort;
    }

    public boolean getTitle(StringBuffer sb, Node node)
    {
        if ("body".equalsIgnoreCase(node.getNodeName()))
            return false;
        if (node.getNodeType() == Node.ELEMENT_NODE
                && "title".equalsIgnoreCase(node.getNodeName()))
        {
            getText(sb, node);
            return true;
        }
        NodeList children = node.getChildNodes();
        if (children != null)
        {
            int len = children.getLength();
            for (int i = 0; i < len; i++)
                if (getTitle(sb, children.item(i)))
                    return true;

        }
        return false;
    }

    public URL getBase(Node node)
    {
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            if ("body".equalsIgnoreCase(node.getNodeName()))
                return null;
            if ("base".equalsIgnoreCase(node.getNodeName()))
            {
                NamedNodeMap attrs = node.getAttributes();
                for (int i = 0; i < attrs.getLength();)
                {
                    Node attr = attrs.item(i);
                    if (!"href".equalsIgnoreCase(attr.getNodeName()))
                        continue;
                    try
                    {
                        return new URL(attr.getNodeValue());
                    } catch (MalformedURLException e)
                    {
                        i++;
                    }
                }

            }
        }
        NodeList children = node.getChildNodes();
        if (children != null)
        {
            int len = children.getLength();
            for (int i = 0; i < len; i++)
            {
                URL base = getBase(children.item(i));
                if (base != null)
                    return base;
            }

        }
        return null;
    }

    private boolean hasOnlyWhiteSpace(Node node)
    {
        String val = node.getNodeValue();
        for (int i = 0; i < val.length(); i++)
            if (!Character.isWhitespace(val.charAt(i)))
                return false;

        return true;
    }

    private boolean shouldThrowAwayLink(Node node, NodeList children,
            int childLen, LinkParams params)
    {
        if (childLen == 0)
            return params.childLen != 0;
        if (childLen == 1
                && children.item(0).getNodeType() == Node.ELEMENT_NODE
                && params.elName.equalsIgnoreCase(children.item(0)
                        .getNodeName()))
            return true;
        if (childLen == 2)
        {
            Node c0 = children.item(0);
            Node c1 = children.item(1);
            if (c0.getNodeType() == Node.ELEMENT_NODE
                    && params.elName.equalsIgnoreCase(c0.getNodeName())
                    && c1.getNodeType() == Node.TEXT_NODE
                    && hasOnlyWhiteSpace(c1))
                return true;
            if (c1.getNodeType() == Node.ELEMENT_NODE
                    && params.elName.equalsIgnoreCase(c1.getNodeName())
                    && c0.getNodeType() == Node.TEXT_NODE
                    && hasOnlyWhiteSpace(c0))
                return true;
        } else if (childLen == 3)
        {
            Node c0 = children.item(0);
            Node c1 = children.item(1);
            Node c2 = children.item(2);
            if (c1.getNodeType() == Node.ELEMENT_NODE
                    && params.elName.equalsIgnoreCase(c1.getNodeName())
                    && c0.getNodeType() == Node.TEXT_NODE
                    && c2.getNodeType() == Node.TEXT_NODE
                    && hasOnlyWhiteSpace(c0) && hasOnlyWhiteSpace(c2))
                return true;
        }
        return false;
    }

    private URL fixEmbeddedParams(URL base, String target)
            throws MalformedURLException
    {
        if (target.indexOf(';') >= 0 || base.toString().indexOf(';') == -1)
            return new URL(base, target);
        String baseURL = base.toString();
        int startParams = baseURL.indexOf(';');
        String params = baseURL.substring(startParams);
        int startQS = target.indexOf('?');
        if (startQS >= 0)
            target = (new StringBuilder()).append(target.substring(0, startQS))
                    .append(params).append(target.substring(startQS))
                    .toString();
        else
            target = (new StringBuilder()).append(target).append(params)
                    .toString();
        return new URL(base, target);
    }

    public void getOutlinks(URL base, ArrayList<Outlink> outlinks, Node node)
    {
        getOutlinks(base, outlinks, node, null);
    }

    public Outlink hasUrl(String url, ArrayList<Outlink> outlinks)
    {
        if (outlinks == null)
            return null;
        if (url == null)
            return null;
        for (int i = 0; i < outlinks.size(); i++)
        {
            Outlink ol = (Outlink) outlinks.get(i);
            if (url.equals(ol.getToUrl()))
            {
                return ol;
            }
        }
        return null;
    }

    // 取出匹配正则的outlink
    public void getOutlinks(URL base, ArrayList<Outlink> outlinks, Node node,
            String regex)
    {
        NodeList children = node.getChildNodes();
        Pattern pattern = null;
        if (regex != null)
        {
            pattern = Pattern.compile(regex);
        }
        int childLen = 0;
        if (children != null)
        {
            childLen = children.getLength();
        }
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            String nodeName = node.getNodeName().toLowerCase();
            LinkParams params = (LinkParams) linkParams.get(nodeName);
            if (params != null)
            {
                if (!shouldThrowAwayLink(node, children, childLen, params))
                {
                    StringBuffer linkText = new StringBuffer();
                    getText(linkText, node, true);
                    NamedNodeMap attrs = node.getAttributes();
                    String target = null;
                    boolean noFollow = false;
                    boolean post = false;
                    for (int i = 0; i < attrs.getLength(); i++)
                    {
                        Node attr = attrs.item(i);
                        String attrName = attr.getNodeName();
                        if (params.attrName.equalsIgnoreCase(attrName))
                        {
                            target = attr.getNodeValue();
                            continue;
                        }
                        if ("rel".equalsIgnoreCase(attrName)
                                && "nofollow".equalsIgnoreCase(attr
                                        .getNodeValue()))
                        {
                            noFollow = true;
                            continue;
                        }
                        if ("method".equalsIgnoreCase(attrName)
                                && "post".equalsIgnoreCase(attr.getNodeValue()))
                            post = true;
                    }

                    if (target != null && !noFollow && !post)
                        try
                        {
                            URL url = base.toString().indexOf(';') <= 0 ? new URL(
                                    base, target) : fixEmbeddedParams(base,
                                    target);
                            boolean badd = true;
                            if (pattern != null)
                            {
                                Matcher m = pattern.matcher(url.toString());
                                // 只有匹配正则的url才进行收集
                                badd = m.matches();
                            }
                            if (badd)
                            {
                                Outlink ool = hasUrl(url.toString(), outlinks);
                                Outlink ol = null;
                                if (ool == null)
                                {
                                    ol = new Outlink(url.toString(), linkText
                                            .toString().trim());
                                } else
                                {
                                    ol = new Outlink(url.toString(),
                                            ool.getAnchor()
                                                    + "@"
                                                    + linkText.toString()
                                                            .trim());
                                    outlinks.remove(ool);
                                }
                                outlinks.add(ol);
                            }
                        } catch (MalformedURLException e)
                        {
                        }
                }
                if (params.childLen == 0)
                {
                    return;
                }
            }
        }
        for (int i = 0; i < childLen; i++)
        {
            getOutlinks(base, outlinks, children.item(i), regex);
        }
    }
}
